Skip to content

feat: add express.static dotfiles codemod#142

Open
singhvishalkr wants to merge 4 commits into
expressjs:mainfrom
singhvishalkr:feat/static-dotfiles-codemod
Open

feat: add express.static dotfiles codemod#142
singhvishalkr wants to merge 4 commits into
expressjs:mainfrom
singhvishalkr:feat/static-dotfiles-codemod

Conversation

@singhvishalkr
Copy link
Copy Markdown

In Express 5, the express.static middleware's dotfiles option defaults to "ignore" instead of serving them. This breaks functionality that depends on serving dot-directories like .well-known (used for Android App Links, Apple Universal Links, Let's Encrypt verification, etc.).

This PR adds a new codemod that explicitly sets dotfiles: 'allow' on express.static() calls that don't already specify a dotfiles option. A comment is included to flag the change for manual review.

The codemod:

  • Transforms express.static('public') to express.static('public', { dotfiles: 'allow' /* Express 5: preserve v4 behavior */ })
  • Preserves existing options when present
  • Skips calls that already have an explicit dotfiles setting

The README includes a security note encouraging developers to review each call and consider whether serving dotfiles is actually necessary.

Also added the new codemod to the v5-migration-recipe workflow.

Closes #116

Add a codemod that explicitly sets dotfiles: 'allow' on express.static()
calls to preserve Express 4 behavior where dotfiles were served by default.

In Express 5, the dotfiles option defaults to 'ignore', which can break
functionality that depends on serving dot-directories like .well-known
(used by Android App Links and Apple Universal Links).

Closes expressjs#116
Copy link
Copy Markdown
Contributor

@AugustinMauroy AugustinMauroy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good starting point

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to bump minor version of this codemod 😁

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I bumped the codemod package version to 1.1.0 in the follow-up commit.


This codemod adds an explicit `dotfiles: 'allow'` option to `express.static()` calls that don't already specify a `dotfiles` option, preserving the Express 4 behavior.

## Example
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO diff codeblock is easier to get what happened

Comment on lines +10 to +11
{ pattern: 'express.static($PATH)' },
{ pattern: 'express.static($PATH, $OPTS)' },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this ins't resolving import alias ...

before doing creazy thing wait @bjohansebas response.
Do you have any function to resolve express import in different cases

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's any existing function to resolve that. I think the Node Userland Migration project has something we could take inspiration from, right?

But we should identify Express imports, because it's not always express.static; people can import it under different names.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added binding resolution for default, namespace, and CommonJS express imports before rewriting static() calls, and the multiline fixture is covered in tests.


if (!nodes.length) return null

const edits: Edit[] = []
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const edits: Edit[] = []

import type { Edit, SgRoot } from '@codemod.com/jssg-types/src/main'

async function transform(root: SgRoot<Js>): Promise<string | null> {
const rootNode = root.root()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const rootNode = root.root()
const rootNode = root.root()
const edits: Edit[] = []

Comment on lines +28 to +38
if (optsText.includes('dotfiles')) {
continue
}

if (optsText.startsWith('{') && optsText.endsWith('}')) {
const inner = optsText.slice(1, -1).trim()
const newOpts = inner
? `{ ${inner}, dotfiles: 'allow' /* Express 5: preserve v4 behavior */ }`
: `{ dotfiles: 'allow' /* Express 5: preserve v4 behavior */ }`
edits.push(call.replace(`express.static(${pathArg.text()}, ${newOpts})`))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seem weak. add more test case with strange indentation + multi line

Detect default, namespace, and CommonJS express bindings before rewriting static() calls. Add a multiline fixture to cover indentation-sensitive options objects.
Match the reviewer request to bump the codemod minor version alongside the alias-handling fix.
@singhvishalkr
Copy link
Copy Markdown
Author

Pushed 559f3d9. I now resolve default, namespace, and CommonJS bindings for express before rewriting static() calls, added a multiline fixture to cover indentation-sensitive options, and bumped the codemod version to 1.1.0.

Comment on lines +51 to +54
const defaultImportPattern = /import\s+([A-Za-z_$][\w$]*)(?:\s*,[\s\S]*?)?\s+from\s+['"]express['"]/g
const namespaceImportPattern = /import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s+['"]express['"]/g
const defaultAsImportPattern = /import\s+\{\s*default\s+as\s+([A-Za-z_$][\w$]*)[\s\S]*?\}\s+from\s+['"]express['"]/g
const requirePattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*require\s*\(\s*['"]express['"]\s*\)/g
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use regex.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed a1a6b47. I replaced the alias scan with AST/definition-based resolution, removed the regex path, and added a regression for require(express).static(...) so the commonjs case stays covered.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed a1a6b47. The alias lookup now uses AST/definition resolution only, the regex path is gone, and the commonjs case has a direct regression test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

new codemod: express.static dotfiles

3 participants